//= require "datastructure"
//= require "splitpane"
//= require "groupedlist"
//= require "ungroupedlist"
//= require "information"

/* MusicNet Alignment Tool
 *
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * == alignment ==
 *  
 * The Main Section 
 **/

/** section: alignment
 * class AlignmentTool
 *  
 * The main AlignmentTool Controller Object
 *  
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 **/

/**
 * new AlignmentTool()
 *  
 **/

var AlignmentTool = Class.create({
	initialize: function()
	{
		window.tool = this;
		
		this.lh_surface = $('lhscontainer');
		this.rh_surface = $('rhs');
		
		this.info_surface = $('rhs');
		
		this.ungrouped_list = new UngroupedList({
			listViewClass: 'ListViewUnGrouped'
		});
		this.ungrouped_list.focus();
		this.lh_surface.insert(this.ungrouped_list.getSurface());

		this.grouped_list = new GroupedList({
			listViewClass: 'ListViewGrouped',
			dataURL: 'ajax/grouped'
		});		
		this.lh_surface.insert(this.grouped_list.getSurface());
		
		this.infoview = new InfoView();
		this.info_surface.insert(this.infoview.getSurface());
		
		// Create keyboard shortcuts for functions
		
		Event.observe(window, 'keydown', function(event){
			
			// else if(event.keyCode == 83) // s
			// 	document.fire('data:save');
			if(event.keyCode == 9) // tab
			{
				event.stop();
				// toggle the focus
				if(this.ungrouped_list.getSurface().hasClassName('focus'))
				{
					this.ungrouped_list.getSurface().removeClassName('focus');
					this.grouped_list.getSurface().addClassName('focus');
				}
				else
				{
					this.ungrouped_list.getSurface().addClassName('focus');
					this.grouped_list.getSurface().removeClassName('focus');
				}
			}
			
			//if(event.keyCode == 38 /* UP */ || event.keyCode == 40 /* DOWN */ || event.keyCode == 39 /* RIGHT */)
			//{
				
				// Pass on the keypress to the infocus list
				if(this.ungrouped_list.getSurface().hasClassName('focus'))
					this.ungrouped_list.keyPress(event);
				else
					this.grouped_list.keyPress(event);
					
			//}
				
			//console.debug(event);
		}.bind(this));
		
		$('loading').hide();
		
		document.observe('loading:show', function(event) { this.showLoading(event.memo) }.bindAsEventListener(this));
		document.observe('loading:hide', this.hideLoading.bindAsEventListener(this));
		
		if(window.location.hash)
		{
			var h = window.location.hash.substr(1, window.location.hash.length-1);
			this.hash = h.toQueryParams();
		}
		else
			this.hash = {};
		
		if(this.hash.skin)
		{
			var fileref=document.createElement("link");
			fileref.setAttribute("rel", "stylesheet");
			fileref.setAttribute("type", "text/css");
			fileref.setAttribute("href", "css/"+this.hash.skin+".css");
			document.getElementsByTagName("head")[0].appendChild(fileref);
		}
	},
	
/**
 * AlignmentTool#showLoading(string) -> null
 * - string (String): A message to display whilst loading
 * 
 **/
	
	showLoading: function(string)
	{
		if(string && Object.isString(string))
			$('loading').update(string);
			
		$('loading').show();
	},
	
/**
 * AlignmentTool#hideLoading() -> null
 * 
 **/
	
	hideLoading: function()
	{
		$('loading').hide();
	}
});

document.observe('dom:loaded', function(){
	new AlignmentTool();
})/* MusicNet Alignment Tool
 *
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

/** section: alignment
 * class MatchedObject
 *  
 **/

// A single item
var MatchedObject = Class.create({
	initialize: function(label, id)
	{
		this.label = label;
		this.id = id;
	},

/**
 * MatchedObject#toString() -> String
 * 
 * Get a string representation of the object
 **/
	
	toString: function()
	{
		return this.label;
	}
});

/** section: alignment
 * class MatchedGroup
 *  
 * A group of matched items (all items deemed to be the same)
 *  
 **/

var MatchedGroup = Class.create({
	initialize: function(items)
	{
		if(!Object.isArray(items))
			items = [ items ];
			
		this.items = items;
		
		this.items.sort();
		
		var uid = "";
		var firstloop = true;
		this.items.each(function(item){
			if(!firstloop)
				uid += "_";
			uid += (item.id+"");
			firstloop = false;
		});
		
		this.id = uid;	// Give each group a unique id;
	},

/**
 * MatchedGroup#addItem() -> null
 * 
 * Add an item to the group
 **/
	
	addItem: function(item)
	{
		this.items.push(item);
		this.items.sort();
	},

/**
 * MatchedGroup#removeItem() -> null
 * 
 * Remove an item from the group
 **/
	
	removeItem: function(item)
	{
		// find the item to remove
		var i=0;
		var index = -1;
		
		this.items.each(function(c_item){
			if(item.id == c_item.id)
				index = i;
				
			i++;
		});
		
		if(index > -1)
			this.items.splice(index, 1);
	},
	
	toString: function()
	{
		if(this.items.length)
			return this.items[0].toString();
		else
			return "";
	}
});

var MatchedList = Class.create({
	all_groups: new Array(),
	all_groups_index: new Object(), // item.id => group.id
	all_groups_lookup: new Object(),// group.id => all_groups index
	all_groups_a2z: new Object(),	// group.id => A2Z index e.g. "a"
	
	az_indexes: null,
	
	initialize: function()
	{
		this.az_indexes = new Object();

		for(var i=65; i<=90; i++)
			this.az_indexes[String.fromCharCode(i).toLowerCase()] = new Array();

		this.az_indexes['#'] = new Array();
	},
	
	add: function(group)
	{
		this.all_groups.push(group);
		this.all_groups_lookup[group.id] = this.all_groups.length-1;
		
		group.items.each(function(item){
			this.all_groups_index[item.id] = group.id;
		}.bind(this));
		
		var chr = group.toString().substr(0,1).toLowerCase();
		
		this.az_indexes[chr].push(group);
		
		this.az_indexes[chr].sort();
		
		this.all_groups_a2z[group.id] = chr;
		
		return group;
	},
	
	remove: function(group)
	{
		var index = this.all_groups_lookup[group.id];
		
		// Remove from the master list
		this.all_groups.splice(index, 1);
		delete this.all_groups_lookup[group.id];
		
		// Remove from the A2Z indexes
		var i=0;
		var index=-1;
		
		var az = this.az_indexes[this.all_groups_a2z[group.id]];
		
		while(i < az.length)
		{
			if(az[i].id == group.id)
				index = i;
				
			i++;
		}
		
		if(index > -1)
		{
			az.splice(index, 1);
			delete this.all_groups_a2z[group.id];
		}
	},
	
	addItemToGroup: function(item, group)
	{
		this.all_groups_index[item.id] = group.id;
		group.addItem(item);
	},
	
	groupWithID: function(id)
	{
		var index = this.all_groups_lookup[id];
		
		if(index !== false)
			return this.all_groups[index];
		else
			return false;
	},
	
	groupForItem: function(item)
	{
		var id = this.all_groups_index[item.id];
		return this.groupWithID(id);
	},
	
	save: function()
	{
		//console.log(this.all_groups.toJSON());
		document.fire('loading:show', "Saving...");
		
		setTimeout( function() { document.fire('loading:hide') }, 1000);
		
		if(localStorage)
		{
			localStorage.setItem('matches', this.all_groups.toJSON());
		}
		else
			alert('saving not supported in this browser!');
	},
});

var DataStructure = new Class.create({
	all_data: null,						// Store all the raw data from the server
	all_data_index: new Object(),		// Provide a way to access raw data by ID
	
	az_indexes: null,					// A-Z Index for all raw data
	
	matched_data: new MatchedList(),			// List of matched objects
	matched_data_lookup: new Object(),	// List of ID's that have been matched
	
	hash_to_group: new Object(),		// hash of labels to groups (help with future processing)
	
	initialize: function(data)
	{
		this.all_data = data;
		
		// Iterate through all the data and build a hash to lookup objects based on ID's
		for(var i=0; i<this.all_data.length; i++)
			this.all_data_index[this.all_data[i].id] = i;
		
		this.indexData();
		
		if(localStorage)
		{
			var matches = localStorage.getItem('matches');
			
			if(matches)
			{
				matches = matches.evalJSON();
				matches.each(function(group){
					var items = new Array();
					
					group.items.each(function(item){
						items.push(new MatchedObject(item.label, item.id));
					});
					
					this.addMatches(items);
				}.bind(this));
			}
		}
		
		this.findMatches();
		
		document.observe('data:save', this.save.bindAsEventListener(this));
	},
	
	save: function(event)
	{
		this.matched_data.save();
	},
	
	itemWithID: function(id)
	{
		var index = this.all_data_index[id];
		
		if(!index)
			return false;
			
		return this.all_data[index];
	},
	
	findMatches: function()
	{
		// Try and do some basic guess work at alignments
		var hash_store = new Object();
		var matches = new Object();

		this.all_data.each(function(item){
			if(!this.matched_data_lookup[item.id])
			{
				// Create a hash of the label
				var hash = item.label.replace(/[^A-Za-z]/g, "").toLowerCase();

				var obj_to_store = new MatchedObject(item.label, item.id);
				
				// See if a group already exists from a previous session
				var g_id = this.hash_to_group[hash];
				
				if(!Object.isUndefined(g_id))
				{
					var group = this.matched_data.groupWithID(g_id);
					if(group)
						this.addItemToGroup(obj_to_store, group);
				}
				else
				{
					if(hash_store[hash])
					{
						// Hash is a match
						hash_store[hash].items.push(obj_to_store);
						matches[hash] = true;
					}
					else
					{
						// Hash does not match
						hash_store[hash] = new Object();
						hash_store[hash].items = new Array();

						hash_store[hash].items.push(obj_to_store);
					}
				}
			}
		}.bind(this));
		
		// Add the items we think are matches
		for(var hash in matches)
		{
			var items = hash_store[hash].items;
			this.addMatches(items);
		}
	},
	
	addMatches: function(items)
	{
		var group = new MatchedGroup(items);
		
		this.matched_data.add(group);
		
		group.items.each(function(item){
			// Mark the item as matched
			this.matched_data_lookup[item.id] = true;
			
			// Make a reference between the hash and the group id for future processing
			var hash = item.label.replace(/[^A-Za-z]/g, "").toLowerCase();
			this.hash_to_group[hash] = group.id
		}.bind(this));
	},
	
	addItemToGroup: function(item, group)
	{
		this.matched_data_lookup[item.id] = true;
		
		this.matched_data.addItemToGroup(new MatchedObject(item.label, item.id), group);
	},
	
	removeItemFromGroup: function(item)
	{
		var group = this.matched_data.groupForItem(item);
		group.removeItem(item);
		this.matched_data_lookup[item.id] = false;
		
		if(group.items.length == 0)
		{
			this.matched_data.remove(group);
		}
	},

	indexData: function()
	{
		// Create arrays for each A-Z
		this.az_indexes = new Object();

		for(var i=65; i<=90; i++)
			this.az_indexes[String.fromCharCode(i).toLowerCase()] = new Array();

		this.az_indexes['#'] = new Array();

		var i=0;
		this.all_data.each(function(item){
			var az = item.label.substr(0,1).toLowerCase();
			if(/[^a-z]/.test(az))
			{
				// Convert special chars to base letters (e.g. à => a)
				var cc = az.charCodeAt(0);
				if(cc >= 224 && cc <= 230)
					az = "a";
				else if(cc == 231)
					az = 'c';
				else if(cc >= 232 && cc <= 235)
					az = 'e';
				else if(cc >= 236 && cc <= 239)
					az = 'i';
				else if(cc == 241)
					az = 'n';
				else if(cc >= 242 && cc <= 246)
					az = 'o';
				else if(cc >= 249 && cc <= 252)
					az = 'u';
				else if(cc == 253)
					az = 'y';
				else if(cc == 254)
					az = 'p';
				else 
					az = '#';
			}

			this.az_indexes[az].push({id: item.id, label:item.label});
		}.bind(this));
	}
});//= require "list"
//= require "listview_grouped"

/* MusicNet Alignment Tool
 *
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

/** section: alignment
 * class GroupedList < ListContainer
 * 
 * Specialised container for list of Grouped items 
 **/

var GroupedList = new Class.create(ListContainer, {
	initialize: function($super, options)
	{
		this.tag = "grp";
		this.id = "groupedlist";
		this.verified = true;
		$super(options);
		
		this.toggle = Builder.node('ul', {className: 'toggle'}, [
			Builder.node('li', {className: 'verified first selected'}, 'Verified'),
			Builder.node('li', {className: 'unverified last'}, 'Un-verified')
		]);
		
		this.toggle.observe('click', function(event){
			event.stop();
			this.toggleVerified();
		}.bindAsEventListener(this));
		
		this.bodyDiv.insert(this.toggle);
		
		document.observe('group:added', function(event){
			if(event && event.memo)
			{
				var data = event.memo;
				
				for(var i=0; i<data.length; i++)
				{
					if(this.currentPage == data[i].items[0].a2z)
					{
						this.data.push({
							id: data[i].match_id,
							label: data[i].items[0].label,
							items: data[i].items
						});
					
						this.data.sort(function(a, b){
							return b.label < a.label;
						});
					
						this.listView.render();
						this.listView.scrollToID(data[i].match_id);
						//this.listView.addGroup(data.match_id, data.items);
					}					
				}

			}
		}.bindAsEventListener(this));
	},

/**
 * GroupedList#toggleVerified() -> null
 * 
 * Toggle the visibility of verified items
 **/
	
	toggleVerified: function()
	{
		var selected = this.toggle.getElementsBySelector(".selected")[0];
		
		if(selected.hasClassName('verified'))
		{
			this.verified = false;
			selected.removeClassName('selected');
			this.toggle.getElementsBySelector(".unverified")[0].addClassName('selected');
		}
		else
		{
			this.verified = true;
			selected.removeClassName('selected');
			this.toggle.getElementsBySelector(".verified")[0].addClassName('selected');
		}
		
		this.loadData(this.currentPage);
	},

/**
 * GroupedList#loadData(page, callback) -> null
 * - page (String): The A2Z page to load
 * - callback (function): The function to perform once the load has completed
 * 
 * Load the items from the given page
 **/
	
	loadData: function(page, callback)
	{
		this.listView.setLoading();
		this.updateA2Z();
		
		new Ajax.Request(window.location.pathname+this.options.dataURL+(this.verified?'/verified':'/unverified'), {
			method: 'get',
			parameters: 'a2z='+page,
			onSuccess: function(transport) {
				this.data = transport.responseJSON.data;
				this.refresh();
				
				if(Object.isFunction(callback))
					callback.bind(this)();
			}.bind(this)
		});
	},
	
	keyPress: function($super, event)
	{
		if(event.shiftKey && event.keyCode == 86) // v
		{
			event.stop();
			this.toggleVerified();
		}
		else if(event.keyCode == Event.KEY_RETURN || event.keyCode == 89) // y
		{
			if(!this.verified)
			{
				// Verify the current group
				this.groupVerify();
			}
		}
		else if(event.keyCode == 78) // n
		{
			this.groupRemove();
			event.stop();
		}
		else if(event.keyCode == 77) // m
		{
			this.groupsMerge();
			event.stop();
		}
		else
			$super(event);
	},

/**
 * GroupedList#groupsMerge() -> null
 * 
 * Merge the selected groups into one
 **/
	
	groupsMerge: function()
	{
		var selected = this.listView.getSelectedIndexes();
				
		var ids_string = "";
		
		for(var index in selected)
		{					
			if(selected[index])
			{
				ids_string += "&gid[]="+this.data[index].id;
				
				this.listView.list.childElements()[index].setLoading();
			}
		}
		
		var scrollTop = this.listView.surface.scrollTop;
		var selectedElement = this.listView.getSelectedElements()[0].previous().id.split('-').shift();

		new Ajax.Request(window.location.pathname+"ajax/group_merge", {
			method: 'get',
			parameters: ids_string,
			onSuccess: function(transport) {
				console.log(selectedElement);
				this.loadData(this.currentPage, function() {
					this.listView.surface.scrollTop = scrollTop;
					this.listView.selectRow($$("."+transport.responseJSON.data.new_group).shift());
				});
			}.bind(this)
		});
	},

/**
 * GroupedList#groupRemove() -> null
 * 
 * Remove the selected groups
 **/
	
	groupRemove: function()
	{
		var selected = this.listView.getSelectedIndexes();
				
		var ids_string = "";
		
		for(var index in selected)
		{					
			if(selected[index])
			{
				ids_string += "&gid[]="+this.data[index].id;
				
				//this.listView.list.childElements()[index].setLoading();
			}
		}
		
		new Ajax.Request(window.location.pathname+"ajax/group_remove", {
			method: 'get',
			parameters: ids_string,
			onSuccess: function(transport) {
				transport.responseJSON.data.groups.each(function(group){
					$$("."+group.id).each(function(elem){
						elem.setUnVerified();
					}.bind(this));
				});
				
				// Should we refresh the ungrouped list?
				document.fire('group:removed', this.currentPage);
			}.bind(this)
		});
	},

/**
 * GroupedList#groupVerify() -> null
 * 
 * Marked the selected groups as verified
 **/
	
	groupVerify: function()
	{
		var selected = this.listView.getSelectedIndexes();
				
		var ids_string = "";
		
		for(var index in selected)
		{					
			if(selected[index])
			{
				ids_string += "&gid[]="+this.data[index].id;
				
				//this.listView.list.childElements()[index].setLoading();
			}
		}
		
		new Ajax.Request(window.location.pathname+"ajax/group_verify", {
			method: 'get',
			parameters: ids_string,
			onSuccess: function(transport) {
				transport.responseJSON.data.groups.each(function(group){
					$$("."+group.id).each(function(elem){
						elem.setVerified();
					}.bind(this));
				});
			}.bind(this)
		});
	},


/**
 * GroupedList#removeItemFromGroup(item_id, group_id) -> null
 * - item_id (String): The item ID
 * - group_id (String): The group ID
 *  
 * Remove an item from a group
 **/
	
	removeItemFromGroup: function(item_id, group_id)
	{
		var elem = $('grouped-'+item_id);
		
		new Effect.Fade(elem, {
			afterFinish: function(params) {
				var group = params.element.up('li');
				
				if(group.childElements().length == 1)
					group.remove();
				else
					params.element.remove();
			}
		});
				
		new Ajax.Request(window.location.pathname+"ajax/group_removeitem", {
			method: 'get',
			parameters: "id="+item_id+"&gid="+group_id,
			onSuccess: function(transport) {
				// Should we refresh the ungrouped list?
				document.fire('group:removed', this.currentPage);
			}.bind(this)
		});
	},
	
	trashSelectedVerify: function()
	{
		var result = confirm("[GROUPED DELETE] Are you sure you want to delete the selected items from the GROUPED list?");
		
		if(result)
			this.trashSelected();
	},
	
	trashSelected: function()
	{
		var selected = this.listView.getSelectedIndexes();
				
		var ids = new Array();
		
		for(var index in selected)
		{	
			var li = this.listView.list.childElements()[index];
			
			li.childElements().each(function(elem){
				ids.push(elem.id.replace('grouped-', ''));
			});				
			
			li.setLoading();
		}
		
		var ids_string = "";
		ids.each(function(id) {
			ids_string += "&id[]="+id;
		});
		
		var scrollTop = this.listView.surface.scrollTop;
		var selectedElement = this.listView.getSelectedElements()[0].previous().id;
		
		new Ajax.Request(window.location.pathname+"ajax/trash_item", {
			method: 'get',
			parameters: ids_string,
			onSuccess: function(transport) {
				this.loadData(this.currentPage, function() {
					this.listView.surface.scrollTop = scrollTop;
					this.listView.selectRow($(selectedElement));
				});
				document.fire("trash:reload");
			}.bind(this)
		});
	},
	
	updateTitle: function()
	{
		this.titleBar.update(Builder.node('div', {}, [
			"Grouped "+this.currentPage.toUpperCase()+"'s ("+this.data.length+" items)"
		]));
	},
	
	getSurface: function()
	{
		return this.surface;
	},
	
	resize: function()
	{
		
	},
	
	singleClick: function($super, event)
	{
		if(event.target.hasClassName('delete'))
		{
			var div = event.target.up();
			var item_id = div.id.split('-').last();
			this.data.removeItemFromGroup(this.data.itemWithID(item_id));
			document.fire('list:refresh');
		}
		else
			$super(event);
	}
});/* MusicNet Alignment Tool
 *
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

/** section: alignment
 * class InfoView
 * 
 * View responsible for displaying the extended metadata
 **/


var InfoView = Class.create({
	initialize: function()
	{
		this.surface = Builder.node('div', {id: 'infoview'});
		
		document.observe('information:view', function(event) { this.loadView(event.memo) }.bindAsEventListener(this));
	},

/**
 * InfoView#loadView(ids) -> null
 * - ids (Array): The item IDs to load
 *  
 * Load information about the provided IDs
 **/
	
	loadView: function(ids)
	{
		var ids_string = "";
		ids.each(function(id) {
			ids_string += "&id[]="+id;
		});
		
		this.setLoading();
		
		new Ajax.Request(window.location.pathname+"ajax/metadata", {
			method: 'get',
			parameters: ids_string,
			onSuccess: function(transport) {
				this.surface.update(transport.responseText);
			}.bind(this)
		});
	},

/**
 * InfoView#setLoading(loading) -> null
 * - loading (Boolean): Whether to hide/show the loading indicator
 *  
 * Show/hide the loading indicator
 **/
		
	setLoading: function(loading)
	{
		if(Object.isUndefined(loading))
			loading = true;
			
		if(loading)
		{
			this.surface.update(Builder.node('div', {className: 'loading'}, 'Loading'));
		}
		else
		{

		}
	},

/**
 * InfoView#getSurface() -> null
 *  
 * Gets the drawing surface for this object
 **/
	
	getSurface: function()
	{
		return this.surface;
	}
});//= require "listview"

/* MusicNet Alignment Tool
 *
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

/** section: alignment
 * class ListContainer
 * 
 * Generic container for list of items 
 **/

var ListContainer = Class.create({	
	initialize: function(options)
	{	
		this.options = Object.extend({
			listViewClass: 'ListView',
			dataURL: 'ajax/ungrouped'
		}, options);
			
		this.surface = Builder.node('div', {id: this.id, className: 'list'});
		
		this.titleBar = Builder.node('div', {className: 'title'});
		this.surface.insert(this.titleBar);
		
		this.bodyDiv = Builder.node('div', {className: 'body'});
		this.surface.insert(this.bodyDiv);
		
		this.az = Builder.node('ul', {className: 'a2z'});
		this.bodyDiv.insert(this.az);
		
		this.listView = new window[this.options.listViewClass](this);
		this.bodyDiv.insert(this.listView.getSurface());
		
		this.currentPage = 'a';
		
		this.renderA2Z();
		
		this.loadData(this.currentPage);
		
		this.az.observe('click', function(event) {
			this.showPage(event.target.innerHTML);
		}.bind(this));
		
		this.surface.observe('click', function(event) {
			event.stop();
			this.focus();
		}.bind(this));
	},

/**
 * ListContainer#focus() -> null
 * 
 * Give focus to the list
 **/
	
	focus: function()
	{
		$$(".list.focus").each(function(elem){
			elem.removeClassName('focus');
		});
		
		this.surface.addClassName('focus');
	},

	
	// Never currently called as both Grouped and Ungrouped Lists overload
	loadData: function(page)
	{
		this.listView.setLoading();
		this.updateA2Z();
		
		new Ajax.Request(window.location.pathname+this.options.dataURL, {
			method: 'get',
			parameters: 'a2z='+page,
			onSuccess: function(transport) {
				this.data = transport.responseJSON.data;
				this.refresh();
			}.bind(this)
		});
	},

/**
 * ListContainer#refresh() -> null
 * 
 * Refresh the list
 **/
	
	refresh: function()
	{			
		this.listView.render();
		this.updateTitle();
	},

/**
 * ListContainer#renderA2Z() -> null
 * 
 * Render the A2Z list on the left of the column
 **/
	
	renderA2Z: function()
	{
		this.az.update();
		
		for(var i=65; i<=90; i++)
		{
			var c = String.fromCharCode(i).toLowerCase();
			var node = Builder.node('li', {id: this.tag+'-az-'+c, title: c.toUpperCase()+"'s"}, c);
			
			node.addClassName('enabled');
				
			if(c == this.currentPage)
				node.addClassName('selected');
			
			this.az.insert(node);
		}
	},

/**
 * ListContainer#updateA2Z() -> null
 * 
 * Update the currently selected A2Z items
 **/
	
	updateA2Z: function()
	{
		if($(this.tag+'-az-'+this.currentPage))			
		{
			this.az.getElementsBySelector('.selected').invoke('removeClassName', 'selected');	
			$(this.tag+'-az-'+this.currentPage).addClassName('selected');
		}
	},

/**
 * ListContainer#updateTitle() -> null
 * 
 * Update the list title
 **/
	
	updateTitle:function()
	{
		this.titleBar.update("List ("+this.data.length+" items)");
	},

/**
 * ListContainer#showPage(page) -> null
 * - page (String): The character of the page to display (a-z)
 *  
 * Show a specific A2Z page of the list
 **/
	
	showPage: function(page)
	{
		if(this.currentPage != page)
		{
			this.currentPage = page;
			this.loadData(this.currentPage);
		}
	},

/**
 * ListContainer#selectRowWithIndex(index) -> null
 * - index (Integer): The row index
 *  
 * Select the row with the given index
 **/
	
	selectRowWithIndex: function(index)
	{
		var li = this.list.childElements()[index];
		this.selectRow(li);
	},

/**
 * ListContainer#selectRow(li, addSelection) -> null
 * - li (HTMLDom): The <li> item to select
 * - addSelection (Boolean): Should the li be added to the current selection
 * 
 * Select a row
 **/
	
	selectRow: function(li, addSelection)
	{
		var id = li.id.split('-').last();
		var index = /row([0-9]+)/.exec(li.className);
		
		if(index)
			index = parseInt(index[1]);
		
		if(!addSelection)
		{
			for(i in this.selected)
			{
				if(this.selected[i])
					$(this.tag+'-listitem-'+i).removeClassName('selected');
			}
			
			this.selected = new Object();
			this.selected_count = 0;
		}
	
		this.selected[id] = true;
		this.selected_count++;
		this.current_index = index;
		
		li.addClassName('selected');
	},

/**
 * ListContainer#unselectRow(li) -> null
 * - li (HTMLDom): The <li> to deselect
 * 
 * Deselect a row
 **/
	
	unselectRow: function(li)
	{
		var index = li.id.split('-').last();
		this.selected[index] = false;
		this.selected_count--;
		li.removeClassName('selected');
	},

/**
 * ListContainer#singleClick(event) -> null
 * - event (Event): Click Event
 *  
 * Click Event handler
 **/
	
	singleClick: function(event)
	{
		var elem = this.getListItemFromEvent(event);
		var id = elem.id.split('-').last();
		
		if(this.selected[id])
			this.unselectRow(elem);
		else
			this.selectRow(elem, (event.metaKey || event.ctrlKey));
	},

/**
 * ListContainer#keyPress(event) -> null
 * - event (Event): KeyPress Event
 *  
 * KeyPress Event handler
 **/
	
	keyPress: function(event)
	{
		if(event.shiftKey && event.ctrlKey)
		{
			event.stop();
			
			if(event.keyCode >= 65 && event.keyCode <= 90) // a-z
			{
				var c = String.fromCharCode(event.keyCode).toLowerCase();
				this.showPage(c);
			}
		}
		
		if(event.keyCode == 39)	// RIGHT
		{
			event.stop();
			this.loadMetadata();
		}
		else if(event.keyCode == Event.KEY_DELETE)
		{
			this.trashSelectedVerify();
		}
		else
			this.listView.keyPress(event);
	},

/**
 * ListContainer#trashSelectedVerify() -> null
 *  
 * Prompt the user for if they wish to delete the selected items
 **/
	
	trashSelectedVerify: function()
	{
		var result = confirm("Are you sure you want to delete the selected items?");
		
		if(result)
			this.trashSelected();
	},

/**
 * ListContainer#trashSelected() -> null
 *  
 * Deletes the selected items
 **/
	
	trashSelected: function()
	{
		
	},

/**
 * ListContainer#loadMetadata() -> null
 *  
 * Loads the metadata about the selected items
 **/
	
	loadMetadata: function()
	{
		var selected = this.listView.getSelectedIndexes();
		
		var ids = new Array();
		
		for(var index in selected)
		{
			if(selected[index])
				ids.push(this.data[index].id);
		}
		
		document.fire('information:view', ids);
	},

/**
 * ListContainer#getSurface() -> null
 *  
 * Gets the drawing surface for this object
 **/
	
	getSurface: function()
	{
		return this.surface;
	},

/**
 * ListContainer#getListItemFromEvent() -> null
 *  
 * Helper function to get the <li> item that initiated an event
 **/
	
	getListItemFromEvent: function(event)
	{
		var elem = event.target;
		
		if(elem.tagName != 'LI')
			elem = elem.up('li');
			
		return elem;
	}
})/* MusicNet Alignment Tool
 *
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

/** section: alignment
 * class ListView
 * 
 * Generic view for rendering list items 
 **/

var ListView = Class.create({
	initialize: function(delegate)
	{
		this.delegate = delegate;
		this.surface = Builder.node('div', {className: 'datacontainer'});
		this.list = Builder.node('ul', {className: 'data', style: 'position: relative'});
		this.surface.insert(this.list);
		
		this.surface.observe('mousedown', this.singleClick.bindAsEventListener(this));
		
		this.selected = new Object();
		this.selectedCount = 0;
		this.currentIndex = 0;
	},

/**
 * ListView#singleClick(event) -> null
 * - event (Event): Click Event
 *  
 * Click Event handler
 **/
	
	singleClick: function(event)
	{
		var elem = event.target;
		
		if(elem.tagName != 'LI')
		{
			elem = elem.up('li');
			
			if(!elem)
				return;
		}
			
		if(this.rowIsSelected(elem))
		{
			if(event.metaKey || event.ctrlKey)
				this.deselectRow(elem);
		}
		else
			this.selectRow(elem, (event.metaKey || event.ctrlKey));
	},

/**
 * ListView#selectRow(li, addSelection) -> null
 * - li (HTMLDom): The <li> item to select
 * - addSelection (Boolean): Should the li be added to the current selection
 * 
 * Select a row
 **/
	
	selectRow: function(li, addSelection)
	{		
		if(!addSelection)
		{
			for(i in this.selected)
			{
				if(this.selected[i] && this.list.childElements()[i])
					this.list.childElements()[i].removeClassName('selected');
			}
			
			this.selected = new Object();
			this.selectedCount = 0;
		}
	
		this.selected[li.getIndex()] = true;
		this.selectedCount++;
		this.currentIndex = li.getIndex();
		
		li.addClassName('selected');
	},

/**
 * ListView#deselectRow(li) -> null
 * - li (HTMLDom): The <li> to deselect
 * 
 * Deselect a row
 **/
	
	deselectRow: function(li)
	{
		if(this.selectedCount == 1)
			return;
			
		this.selected[li.getIndex()] = false;
		this.selectedCount--;
		li.removeClassName('selected');
	},

/**
 * ListView#keyPress(event) -> null
 * - event (Event): KeyPress Event
 *  
 * KeyPress Event handler
 **/
	
	keyPress: function(event)
	{
		if(event.keyCode == 38 /* UP */ || event.keyCode == 40 /* DOWN */)
		{
			event.stop();
			
			// Move up or down
			var prev = this.currentIndex;
			this.currentIndex += (event.keyCode==38 ? -1 : 1);
			
			// range check
			if(this.currentIndex < 0)
				this.currentIndex = 0;
				
			if(this.currentIndex >= this.list.childElements().length)
				this.currentIndex = this.list.childElements().length-1;
			
			if(event.shiftKey)
			{
				var c_li = this.list.childElements()[this.currentIndex];
				if(c_li.hasClassName('selected'))
				{
					var p_li = this.list.childElements()[prev];
					this.deselectRow(p_li);
				}
			}

			var li = this.list.childElements()[this.currentIndex];
			this.selectRow(li, event.shiftKey);
			
			// Make sure the new item is visible
			if(li.offsetTop + li.getHeight() > this.surface.getHeight() + this.surface.scrollTop)
			{
				this.surface.scrollTop = li.offsetTop + li.getHeight() - this.surface.getHeight();
			}
			else if(li.offsetTop < this.surface.scrollTop)
			{
				this.surface.scrollTop = li.offsetTop;
			}
		}
	},

/**
 * ListView#rowIsSelected(elem) -> Boolean
 * - elem (HTMLDom): The <li> to test
 *  
 * Is the <li> item currently selected
 **/
	
	rowIsSelected: function(elem)
	{
		return this.selected[elem.getIndex()];
	},

/**
 * ListView#getSelectedIndexes() -> Object
 *  
 * Get the key/value hash of the selected items
 **/
	
	getSelectedIndexes: function()
	{
		return this.selected;
	},

/**
 * ListView#getValidSelectedIndexes() -> Object
 *  
 * Get the list of the selected items indexes
 **/
	
	getValidSelectedIndexes: function()
	{
		var valid_items = {};
		
		for(var index in this.selected)
		{					
			if(this.selected[index] && !this.list.childElements()[index].hasClassName('grouped'))
			{
				valid_items[index] = true;
			}
		}
		
		return valid_items;
	},

/**
 * ListView#getSelectedElements() -> Array
 *  
 * Get an array of the selected elements
 **/
	
	getSelectedElements: function(indexes)
	{
		if(Object.isUndefined(indexes))
			indexes = this.selected;
			
		var items = [];
		
		for(var index in indexes)
		{					
			if(indexes[index])
				items.push(this.list.childElements()[index]);
		}
		
		return items;
	},

/**
 * ListView#getValidSelectedElements() -> Array
 *  
 * Get an array of all the currently selected elements
 **/
	
	getValidSelectedElements: function()
	{
		return this.getSelectedElements(this.getValidSelectedIndexes());
	},

/**
 * ListView#render() -> null
 *  
 * Render the list
 **/
	
	render: function()
	{
		var newList = Builder.node('ul', {className: 'data', style: 'position: relative'});
		var i=0;
		this.delegate.data.each(function(item){
			var li = this.buildListItem(item, i);
			
			if(li)
			{
				newList.insert(li);
				i++;
			}
		}.bind(this));
		
		this.list.replace(newList);
		this.list = newList;
		
		if(this.delegate.data.length)
			this.selectRow(this.list.childElements()[0]);
	},

/**
 * ListView#buildListItem(item, index) -> HTMLDom
 * - item (Object): The item to render
 * - index (Integer): The index of the item in the list
 *  
 * Helper function to render a single <li> item
 **/
	
	buildListItem: function(item, index)
	{
		if(!item.id || !item.label)
			return false;
			
		var li = Builder.node('li', {id: item.id+'-'+index, className: item.id}, item.label);
		li.getIndex = function() {
			return parseInt(this.id.split('-').last()); 
		};
		li.getID = function() {
			return this.id.split('-').first(); 
		};
		li.setLoading = function(loading) {
			try
			{
				if(Object.isUndefined(loading))
					loading = true;
				
				if(loading && !this.down('.loading'))
				{
					this.insert(Builder.node('div', {className: 'loading'}));
				}
				else
				{
					var elem = this.down('.loading');
				
					if(elem)
						elem.remove();
				}
			}
			catch(e)
			{}
		};
		li.setDisabled = function() {
			this.setLoading(false);
			this.addClassName('grouped');
			new Effect.Fade(this, {
				to: 0.2
			})
		};
		index % 2 == 0 ? li.addClassName('odd') : li.addClassName('even');
		
		return li;
	},

/**
 * ListView#setLoading(loading) -> null
 * - loading (Boolean): Whether the loading indication should be enabled/disabled 
 * 
 * Set the state of the loading indicator
 **/
	
	setLoading: function(loading)
	{
		if(Object.isUndefined(loading))
			loading = true;
			
		if(loading)
		{
			this.list.update();
			this.list.insert(Builder.node('li', {className: 'loading'}, 'Loading'));
		}
		else
		{

		}
	},

/**
 * ListView#scrollToID(id) -> null
 * - id (Integer): The id of the item to scroll into view
 *  
 * Scroll to the item with the given ID
 **/
	
	scrollToID: function(id)
	{
		var item = this.surface.down('.'+id);
		
		var offset_top = item.offsetTop;
		
		var surface_height = this.surface.getHeight();
		
		var total_offset = (offset_top - (0.5*surface_height)) + item.getHeight();
		
		this.surface.scrollTop = total_offset;
		
		Effect.Pulsate(item, {
			pulses: 2,
			duration: 1
		})
	},

/**
 * ListView#getSurface() -> null
 *  
 * Gets the drawing surface for this object
 **/
	
	getSurface: function()
	{
		return this.surface;
	}
})/* MusicNet Alignment Tool
 *
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

/** section: alignment 
 * class ListViewGrouped < ListView
 * 
 * Specialised ListView for rendering Grouped items
 **/

var ListViewGrouped = Class.create(ListView, {	
	initialize: function($super, delegate)
	{
		$super(delegate);
		
		document.observe('group:add', this.dragMerge.bindAsEventListener(this));
						
		this.surface.observe('mouseout', function(event) {
			var target = event.target;
			
			// Find the currently hovered over <li>
			if(target.localName != 'li')
				return;	
				
			$$('.drag_hover').each(function(elem){
				elem.removeClassName('drag_hover');
			});
			this.drop_zone = null;
		}.bindAsEventListener(this));
		
		this.surface.observe('mouseover', function(event){
			if(!window.list_dragging)
				return;
				
			var target = event.target;
			
			// Find the currently hovered over <li>
			if(target.localName != 'li')
				target = target.up('li');
				
			// Have we found an <li>	
			if(target && target.localName == 'li')
			{
				if(!target.hasClassName('drag_hover'))
				{
					$$('.drag_hover').each(function(elem){
						elem.removeClassName('drag_hover');
					})
					target.addClassName('drag_hover');
					this.drop_zone = target;
				}
			}
			else
			{
				$$('.drag_hover').each(function(elem){
					elem.removeClassName('drag_hover');
				})
				this.drop_zone = null;
			}
		}.bindAsEventListener(this));
	},

/**
 * ListViewGrouped#dragMerge(event) -> null
 * - event (Event): Drag Event
 *  
 * Merge the dragged item with the group it was dropped on
 **/
	
	dragMerge: function(event)	
	{
		if(event && event.memo && this.drop_zone)
		{
			
			var params = "";
			
			event.memo.each(function(item){
				params += "&id[]="+item.getID();
				item.setLoading();
			});

			this.drop_zone.setLoading();
			
			params += "&gid=" + this.drop_zone.getID();
			
			new Ajax.Request(window.location.pathname+"ajax/group_add", {
				method: 'get',
				parameters: params,
				onSuccess: function(transport){
					// Update the items
					transport.responseJSON.data.items.each(function(item){
						$$("."+item.id).invoke("setDisabled");
					});
					// Update the group
					$$("."+transport.responseJSON.data.match_id).each(function(elem){
						elem.setLoading(false);
						
						transport.responseJSON.data.items.each(function(sub_item){
							var div = this.buildSubListItem(sub_item.id, sub_item.label);
							elem.insert(div);
						}.bind(this));
					}.bind(this));
				}.bind(this)
			});
		}
		
		this.drop_zone = null;
	},

/**
 * ListViewGrouped#buildSubListItem(id, label, verified) -> HTMLDom
 * - it (String): The ID of the item
 * - label (String): The string representation of the item
 * - verifed (Boolean): Is this item verified or not
 *  
 * Helper function to render each item belonging to a group
 **/
	
	buildSubListItem: function(id, label, verified)
	{
		// if(!verified)
		// 	label = "* "+label;
			
		var div = Builder.node('div', {id: 'grouped-'+id, className: 'grouped_item'}, [
			label,
			Builder.node('div', {className: 'delete'}, 'x')
		]);
		
		div.getID = function() {
			return this.id.split('-').last(); 
		};
		
		return div;
	},
	
	buildListItem: function($super, item, index)
	{
		li = $super(item, index);
		
		if(!li)
			return false;
		
		li.update();
		
		// Insert children of the group
		item.items.each(function(sub_item){
			var div = this.buildSubListItem(sub_item.id, sub_item.label, sub_item.verified);
			li.insert(div);
		}.bind(this));
		
		// Add some custom helper function
		li.setVerified = function(){
			this.setLoading(false);
			this.addClassName('verified');
			
			if(this.down('.verified'))
			{
				var elem = this.down('.verified');
				
				if(elem)
					elem.remove();
			}
			
			if(this.down('.removed'))
			{
				var elem = this.down('.removed');
				
				if(elem)
					elem.remove();
			}
			
			this.insert(Builder.node('div', {className: "verified"}));
			
			new Effect.Fade(this, {
				to: 0.2
			})
		};
		
		li.setUnVerified = function(){
			this.setLoading(false);
			this.addClassName('removed');
			
			if(this.down('.verified'))
			{
				var elem = this.down('.verified');
				
				if(elem)
					elem.remove();
			}
			
			if(this.down('.removed'))
			{
				var elem = this.down('.removed');
				
				if(elem)
					elem.remove();
			}
			
			this.insert(Builder.node('div', {className: "removed"}));
			new Effect.Fade(this, {
				to: 0.2
			})
		};
		
		return li;
	},
	
	// addGroup: function(match_id, items)
	// {
	// 	var index = this.delegate.data.length;
	// 	
	// 	var li = this.buildListItem({id: match_id, items: items, label: items.length?items[0].label:null}, index);
	// 	
	// 	// Do we have a previous instance of this group?
	// 	var old_li = $$("#groupedlist ."+match_id);
	// 	if(old_li.length)
	// 	{
	// 		var old_li = old_li[0];
	// 		
	// 		li.removeClassName('odd');
	// 		li.removeClassName('even');
	// 		
	// 		if(old_li.hasClassName('odd'))
	// 			li.addClassName('odd');
	// 		else
	// 			li.addClassName('even');
	// 			
	// 		old_li.replace(li);
	// 	}
	// 	else
	// 	{
	// 		this.list.insert(li);
	// 		
	// 		// Scroll to last element in the list
	// 		var delta = this.surface.getHeight() - this.list.getHeight();
	// 
	// 		if(delta < 0)
	// 			this.surface.scrollTop = 0-delta;	
	// 	}
	// },
	
	singleClick: function($super, event)
	{
		if(event.target.hasClassName('delete'))
		{
			var elem = event.target.up('.grouped_item');
			var group_elem = event.target.up('li');
			
			if(!group_elem.hasClassName('removed'))
			{
				event.stop();
				
				if(elem && group_elem)
					this.delegate.removeItemFromGroup(elem.getID(), group_elem.getID());
			}
		}
		else
			$super(event);
	}
});/* MusicNet Alignment Tool
 *
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

/** section: alignment 
 * class ListViewUnGrouped < ListView
 * 
 * Specialised ListView for rendering Ungrouped items
 **/

var ListViewUnGrouped = Class.create(ListView, {	
	initialize: function($super, delegate)
	{
		$super(delegate);
		
		var body = document.body;
		
		this.surface.observe('click', function(event){
			
		});
		
		this.surface.observe('mousedown', function(event) { 
			// Ensure the click came from a list element
			var t = event.target;
			
			if(t.localName != 'li')
			{
				t = t.up('li');
				
				if(!t)
					return;
			}
			
			this.start_x = event.pointerX();
			this.start_y = event.pointerY();
			event.stop();
			
			// Only allow dragging of valid items
			var valid_selections = this.getValidSelectedIndexes();
				
			var valid_selections_count = 0;
			
			for(var index in valid_selections)
				valid_selections_count++;
				
			if(valid_selections_count == 0)
				return;
			
			var drag_marker = Builder.node('div', {id: 'drag_marker', style: "display: none; zIndex: 1000; position: absolute; top: "+this.start_y+"px; left: "+this.start_x+"px"}, valid_selections_count+" item"+(valid_selections_count>1?'s':''));
			
			body.insert(drag_marker); // TODO: ensure this is IE compatible
			
			body.observe('mousemove', function(event){
				event.stop();
				window.list_dragging = true;
				
				drag_marker.show();
				drag_marker.setStyle({left: event.pointerX()-drag_marker.getWidth()+'px', top: event.pointerY()-drag_marker.getHeight()+'px'});
				
				body.observe('mouseup', function(event) {
					body.stopObserving('mousemove');
					body.stopObserving('mouseup');
					if($("drag_marker"))
						$("drag_marker").remove();
					window.list_dragging = false;
					
					// Fire a drop event					
					document.fire('group:add', this.getSelectedElements());
				}.bindAsEventListener(this));
			}.bindAsEventListener(this));
		}.bindAsEventListener(this));
		
		this.surface.observe('mouseup', function(event) {
			body.stopObserving('mousemove');
			body.stopObserving('mouseup');
			if($("drag_marker"))
				$("drag_marker").remove();
			window.list_dragging = false;
		}.bindAsEventListener(this));
	},
	
	buildListItem: function($super, item, index)
	{
		var li = $super(item, index);
		
		// new Draggable(li, {
		// 	revert: true,
		// 	onStart: function(){
		// 		window.list_dragging = true;
		// 	},
		// 	onEnd: function(){
		// 		window.list_dragging = false;
		// 		document.fire("group:add", [li]);
		// 	}
		// });
		
		return li;
	}
});/* MusicNet Alignment Tool
 *
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

/** section: alignment 
 * class SplitPane
 * 
 * View for rendering the splitview UI
 **/

var SplitPane = Class.create({
	initialize: function(surface, options)
	{
		if(!window.console || !window.console.log)
		{
			window.console = {
				log: function() {}
			};
		}
		
		this.surface = $(surface);
		
		if(!this.surface)
		{
			console.log("SplitPane: Invalid surface provided");
			return;
		}
		
		this.options = Object.extend({
			min_width: 300
		}, options);
		
		// For all but the last element add a drag handle
		for(var i=0; i<this.surface.childElements().length-1; i++)
		{
			var elem = this.surface.childElements()[i];
			var handle = Builder.node('div', {className: 'draghandle'});
			elem.insert(handle);
			elem.setStyle({position: 'relative'});
			new Draggable(handle, { 
					constraint: 'horizontal', 
					handle: 'draghandle',
					onStart: function(drag_object, event) {
						var lhs = drag_object.element.up();
						var rhs = lhs.next();
						
						this.start_position = event.clientX;
						this.lhs_start_width = lhs.getWidth();
						this.rhs_start_width = rhs.getWidth();
					}.bind(this),
					onDrag: function(drag_object, event) {
						var lhs = drag_object.element.up();
						var rhs = lhs.next();
						var delta = event.clientX - this.start_position;
						
						var new_lhs_width = this.lhs_start_width+delta;
						var new_rhs_width = this.rhs_start_width-delta;
						
						if(new_lhs_width < this.options.min_width || new_rhs_width < this.options.min_width)
							return;
						
						lhs.setStyle({width: new_lhs_width+'px'});
						rhs.setStyle({width: new_rhs_width+'px'});
					}.bind(this),
					onEnd: function(drag_object, event) {
						drag_object.element.setStyle({position: 'absolute', right: '0', left: 'auto', zIndex: '100'});
					}
				});
		}
		
		document.observe('resize', this.resize.bindAsEventListener(this));
	},
	
/**
 * SplitPane#resize(event) -> null
 * - event (Event): Resize Event	
 *  
 * Respond to browser resize events
 **/	
	
	resize: function(event)
	{
		var total_width = 0;
		var child_percentages = new Array();
		this.surface.childElements().each(function(elem){
			total_width += elem.getWidth();
		});
		
		var i = 0;
		this.surface.childElements().each(function(elem){
			child_percentages[i++] = (elem.getWidth() / total_width);
		});
		
		var new_width = this.surface.getWidth();
		var i = 0;
		this.surface.childElements().each(function(elem){
			var percentage = child_percentages[i++];
			elem.setStyle({width: (new_width*percentage)+'px'});
		});
	}
});

document.observe('dom:loaded', function(){
	$$('.splitpane').each(function(elem){
		new SplitPane(elem);
	});
});//= require "list"
//= require "listview_ungrouped"

/* MusicNet Alignment Tool
 *
 * Copyright (c) 2010 Joe Lambert
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */

/** section: alignment
 * class UngroupedList < ListContainer
 * 
 * Specialised container for list of Ungrouped items 
 **/

var UngroupedList = Class.create(ListContainer, {
	initialize: function($super, options)
	{
		this.tag = "ungrp";
		this.id = "ungroupedlist";
		this.trashed = false;
		$super(options);
		
		this.toggle = Builder.node('ul', {className: 'toggle'}, [
			Builder.node('li', {className: 'verified first selected'}, 'Ungrouped'),
			Builder.node('li', {className: 'unverified last'}, 'Trashed')
		]);
		
		this.toggle.observe('click', function(event){
			event.stop();
			this.toggleTrashed();
		}.bindAsEventListener(this));
		
		this.bodyDiv.insert(this.toggle);
		
		document.observe('group:removed', function(event){
			if(event && event.memo)
			{
				var a2z = event.memo;
				
				if(this.currentPage == a2z)
				{
					this.loadData(this.currentPage);
				}
			}
		}.bindAsEventListener(this));
		
		document.observe('trash:reload', function(event){
			if(this.trashed)
				this.loadData(this.currentPage);
		}.bindAsEventListener(this));
	},

/**
 * UngroupedList#toggleTrashed() -> null
 * 
 * Toggle the visibility of trashed items
 **/
	
	toggleTrashed: function()
	{
		var selected = this.toggle.getElementsBySelector(".selected")[0];
		
		if(selected.hasClassName('verified'))
		{
			this.trashed = true;
			selected.removeClassName('selected');
			this.toggle.getElementsBySelector(".unverified")[0].addClassName('selected');
		}
		else
		{
			this.trashed = false;
			selected.removeClassName('selected');
			this.toggle.getElementsBySelector(".verified")[0].addClassName('selected');
		}
		
		this.loadData(this.currentPage);
	},

/**
 * UngroupedList#loadData(page, callback) -> null
 * - page (String): The A2Z page to load
 * - callback (function): The function to perform once the load has completed
 * 
 * Load the items from the given page
 **/
	
	loadData: function(page, callback)
	{
		this.listView.setLoading();
		this.updateA2Z();

		new Ajax.Request(window.location.pathname+this.options.dataURL+(this.trashed ? '/trashed' : ''), {
			method: 'get',
			parameters: 'a2z='+page,
			onSuccess: function(transport) {
				this.data = transport.responseJSON.data;
				this.refresh();
				
				if(Object.isFunction(callback))
					callback.bind(this)();
			}.bind(this)
		});
	},
	
	keyPress: function($super, event)
	{
		if(event.keyCode == 71 && !event.ctrlKey && !event.shiftKey) // g
		{
			this.groupSelected();	
			event.stop();
		}
		if(event.keyCode == 83 && !event.ctrlKey && !event.shiftKey) // s
		{
			this.createSingleItemGroups();	
			event.stop();
		}
		if(event.keyCode == 85) // u
		{
			if(this.trashed)
			{
				this.untrashSelected();
			}
		}
		else
			$super(event);
	},

	trashSelected: function()
	{
		var selected = this.listView.getSelectedIndexes();
				
		var ids = new Array();
		
		for(var index in selected)
		{					
			if(selected[index] && !this.listView.list.childElements()[index].hasClassName('grouped'))
			{
				ids.push(this.data[index].id);
				
				this.listView.list.childElements()[parseInt(index)].setLoading();
			}
		}
		
		var ids_string = "";
		ids.each(function(id) {
			ids_string += "&id[]="+id;
		});
		
		var scrollTop = this.listView.surface.scrollTop;
		
		var curElement = this.listView.getSelectedElements()[0];
		
		var selectedElement = curElement.previous();
		
		if(!selectedElement)
			selectedElement = curElement.next();
			
		if(!selectedElement)
			selectedElement = false;
		else
			selectedElement = selectedElement.getID();
		
		new Ajax.Request(window.location.pathname+"ajax/trash_item", {
			method: 'get',
			parameters: ids_string,
			onSuccess: function(transport) {
				this.loadData(this.currentPage, function() {
					this.listView.surface.scrollTop = scrollTop;
					
					var elem = $$("li."+selectedElement);
					
					if(elem.length)
						this.listView.selectRow($$("li."+selectedElement)[0]);
				});
				
			}.bind(this)
		});
	},

/**
 * UngroupedList#untrashSelected() -> null
 * 
 * Restore the selected trashed items
 **/
	
	untrashSelected: function()
	{
		var selected = this.listView.getSelectedIndexes();
				
		var ids = new Array();
		
		for(var index in selected)
		{					
			if(selected[index] && !this.listView.list.childElements()[index].hasClassName('grouped'))
			{
				ids.push(this.data[index].id);
				
				this.listView.list.childElements()[parseInt(index)].setLoading();
			}
		}
		
		var ids_string = "";
		ids.each(function(id) {
			ids_string += "&id[]="+id;
		});
		
		new Ajax.Request(window.location.pathname+"ajax/untrash_item", {
			method: 'get',
			parameters: ids_string,
			onSuccess: function(transport) {
				this.loadData(this.currentPage);
			}.bind(this)
		});
	},

/**
 * UngroupedList#createSingleItemGroups() -> null
 * 
 * Create a group for each item currently selected (groups of 1)
 **/
	
	createSingleItemGroups: function()
	{
		var selected = this.listView.getSelectedIndexes();
				
		var ids = new Array();
		
		var valid = true;
		
		for(var index in selected)
		{					
			if(selected[index] && !this.listView.list.childElements()[index].hasClassName('grouped'))
			{
				ids.push(this.data[index].id);
				
				this.listView.list.childElements()[parseInt(index)].setLoading();
			}
			else
				valid = false
		}
		
		if(!valid)
		{
			for(var index in selected)
			{
				this.listView.list.childElements()[parseInt(index)].setLoading(false);
			}
			
			return;
		}
		
		var ids_string = "";
		ids.each(function(id) {
			ids_string += "&id[]="+id;
		});
		
		new Ajax.Request(window.location.pathname+"ajax/group_createsingles", {
			method: 'get',
			parameters: ids_string,
			onSuccess: function(transport) {
				transport.responseJSON.data.each(function(match){
					match.items.each(function(item){
						$$("."+item.id).each(function(elem){
							elem.setDisabled();
						}.bind(this));	
					}.bind(this));
				}.bind(this));
				
				// Should we add the new group to the grouped list?
				document.fire('group:added', transport.responseJSON.data);
			}.bind(this)
		});		
	},

/**
 * UngroupedList#groupSelected() -> null
 * 
 * Create a group from the selected items
 **/
	
	groupSelected: function()
	{
		var selected = this.listView.getSelectedIndexes();
				
		var ids = new Array();
		
		var valid = true;
		
		for(var index in selected)
		{					
			if(selected[index] && !this.listView.list.childElements()[index].hasClassName('grouped'))
			{
				ids.push(this.data[index].id);
				
				this.listView.list.childElements()[parseInt(index)].setLoading();
			}
			else
				valid = false
		}
		
		if(!valid)
		{
			for(var index in selected)
			{
				this.listView.list.childElements()[parseInt(index)].setLoading(false);
			}
			
			return;
		}
		
		var ids_string = "";
		ids.each(function(id) {
			ids_string += "&id[]="+id;
		});
		
		new Ajax.Request(window.location.pathname+"ajax/group_create", {
			method: 'get',
			parameters: ids_string,
			onSuccess: function(transport) {					
				transport.responseJSON.data[0].items.each(function(item){
					$$("."+item.id).each(function(elem){
						elem.setDisabled();
					}.bind(this));
				}.bind(this));
				
				// Should we add the new group to the grouped list?
				document.fire('group:added', transport.responseJSON.data);
			}.bind(this)
		});
	},
	
	updateTitle: function()
	{
		this.titleBar.update(Builder.node('div', {}, [
			(this.trashed?"Trashed ":"Ungrouped ")+this.currentPage.toUpperCase()+"'s ("+this.data.length+" items)"
		]));
	}
});